package com.chenfan.filestore.service.biz.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chenfan.ccp.common.exception.ApiException;
import com.chenfan.ccp.util.TransactionLifeCycleUtils;
import com.chenfan.filestore.config.DS;
import com.chenfan.filestore.config.DynamicDataSourceContextHolder;
import com.chenfan.filestore.config.FileStoreConfig;
import com.chenfan.filestore.dto.TaskDTO;
import com.chenfan.filestore.dto.TaskQueryDTO;
import com.chenfan.filestore.enums.TaskTypeEnum;
import com.chenfan.filestore.exception.FileStoreCode;
import com.chenfan.filestore.model.BatchExportTemplate;
import com.chenfan.filestore.model.BatchImportTemplate;
import com.chenfan.filestore.model.Task;
import com.chenfan.filestore.mq.task.TaskMqMsg;
import com.chenfan.filestore.service.BatchExportTemplateService;
import com.chenfan.filestore.service.BatchImportTemplateService;
import com.chenfan.filestore.service.TaskService;
import com.chenfan.filestore.service.biz.TaskBiz;
import com.chenfan.filestore.utils.DateUtils;
import com.chenfan.filestore.utils.FileUtil;
import com.chenfan.filestore.utils.FreemarkerUtil;
import com.chenfan.filestore.vo.TaskVO;
import com.chenfan.infra.context.TenantContextHolder;
import com.chenfan.infra.context.UserContextHolder;
import com.xxl.job.core.util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.message.MessageConst;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

import static com.chenfan.filestore.common.Constants.BATCH_EXPORT_TASK_TOPIC;
import static com.chenfan.filestore.common.Constants.BATCH_IMPORT_TASK_TOPIC;

@Service
@RefreshScope
@Slf4j
public class TaskBizImpl implements TaskBiz {

    @Autowired
    private TaskService taskService;
    @Autowired
    private BatchExportTemplateService exportTemplateService;
    @Autowired
    private BatchImportTemplateService importTemplateService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Value(BATCH_EXPORT_TASK_TOPIC)
    private String exportTopic;

    @Value(BATCH_IMPORT_TASK_TOPIC)
    private String importTopic;

    @Autowired
    private FileStoreConfig fileStoreConfig;

    @Override
    public TaskVO get(Long taskId) {
        Task task = taskService.getById(taskId);
        TaskVO taskVO = new TaskVO();
        BeanUtils.copyProperties(task,taskVO);
        return taskVO;
    }

    @Override
    public Page<TaskVO> page(TaskQueryDTO query) {
        Page<TaskVO> taskVOPage = new Page<>(query.getPageNum(), query.getPageSize());
        Page<Task> taskPage = taskService.page(query);
        BeanUtils.copyProperties(taskPage,taskVOPage);
        // 转换 records 列表
        List<TaskVO> taskVOs = new ArrayList<>();
        for (Task task : taskPage.getRecords()) {
            TaskVO taskVO = new TaskVO();
            BeanUtils.copyProperties(task, taskVO);
            taskVOs.add(taskVO);
        }
        taskVOPage.setRecords(taskVOs);
        taskVOPage.getRecords().forEach(taskVO -> {
            if (taskVO.getDoTime() != null && taskVO.getDoneTime() != null) {
                Instant startInstant = taskVO.getDoTime().toInstant();
                Instant endInstant = taskVO.getDoneTime().toInstant();
                Duration duration = Duration.between(startInstant, endInstant);
                double differenceInMinutes = duration.toMillis() / (60.0 * 1000);
                taskVO.setDifferenceTime(Math.round(differenceInMinutes * 10) / 10.0); // 保留一位小数
            }
        });

        return taskVOPage;
    }

    @Override
//    @Transactional 注意此处有用到动态数据源 如果加了事务会导致动态切换数据源时不生效
    @DS
    public Long add(TaskDTO dto) {

        Task task = null;
        if (dto.exportTask()) {
            task = createExportTask(dto);
        }

        if (dto.importTask()) {
            task = createImportTask(dto);
        }

        if (Objects.isNull(task)) {
            return null;
        }

        taskService.save(task);

        // 3、发送异步mq消费消息排队消费 事务提交后再发送mq 不然会导致 消费的时候可能事务还未提交
        Task finalTask = task;

//        TransactionLifeCycleUtils.afterCommitSuccessCallback(
//                () -> sendMq(finalTask)
//        );
        sendMq(finalTask);
        return task.getTaskId();
    }

    private Task createExportTask(TaskDTO dto) {
        // 1、校验获取模板
        BatchExportTemplate exportTemplate = checkExportTemplate(dto);

        // 2、创建任务 注意第二步只能放在前面要得到sql 给后面查数量
        Task task = dealTaskInfo(dto, exportTemplate);
        // 3、带图的校验数量条数 超过1000条不让导出 校验间隔时间1分钟带图导出
        checkTimeLimitAndNum(dto, exportTemplate,task.getSql());
        return task;
    }

    private void checkTimeLimitAndNum(TaskDTO dto, BatchExportTemplate exportTemplate,String sql) {
        if(dto.isShowImage()){
            Long userId = UserContextHolder.getUserVo().getUserId();
            Long templateId = exportTemplate.getTemplateId();
            Task task = new Task();
            task.setCreateBy(userId);
            task.setTemplateId(templateId);
            task.setShowImage(dto.isShowImage());
            Task dbTask = taskService.queryLasteast(task);
            // 如果统一模板相同人导出带图的excel 限定时间一分钟
            if(Objects.nonNull(dbTask) && DateUtils.addSeconds(dbTask.getCreateDate(),fileStoreConfig.getBatchImageInteval()).compareTo(new Date()) > 0){
                throw new ApiException(FileStoreCode.TIME_LIMIT);
            }
            try {
                DynamicDataSourceContextHolder.set(exportTemplate.getDatabaseId()+"");
                Long count = taskService.findByCount(sql);
                if(count > 2000) {
                    throw new ApiException(FileStoreCode.BATCH_IMAGE_NUM_LIMIT);
                }
            } finally {
                DynamicDataSourceContextHolder.clear();
            }
        }
    }

    private Task createImportTask(TaskDTO dto) {
        // 1、校验获取模板
        BatchImportTemplate importTemplate = checkImportTemplate(dto);

        // 2、创建任务
        return dealTaskInfo(dto, importTemplate);
    }


    private void sendMq(Task task){
        TaskMqMsg taskMqMsg = new TaskMqMsg();
        taskMqMsg.setTaskId(task.getTaskId());
        taskMqMsg.setTenantId(TenantContextHolder.getTenantId());
        Message<TaskMqMsg> message = MessageBuilder.withPayload(taskMqMsg)
                .setHeader("token", UserContextHolder.getToken())
                .setHeader(MessageConst.PROPERTY_KEYS, taskMqMsg.getTaskId())
                .setHeader("tenantId", TenantContextHolder.getTenantId()).build();
        SendResult sendResult = null;
        if (TaskTypeEnum.IMPORT.getCode().equals(task.getTaskType())) {
            sendResult = rocketMQTemplate.syncSend(importTopic, message, 2000);
        }
        if (TaskTypeEnum.EXPORT.getCode().equals(task.getTaskType())) {
            sendResult = rocketMQTemplate.syncSend(exportTopic, message, 2000);
        }

        if(Objects.isNull(sendResult) || ! SendStatus.SEND_OK.equals(sendResult.getSendStatus())){
            throw new ApiException(FileStoreCode.TASK_CREATE_EXPORT_ERROR);
        }

    }

    private Task dealTaskInfo(TaskDTO dto,BatchExportTemplate template){
        if(Objects.isNull(dto) || Objects.isNull(dto.getTemplateCode())){
            throw new ApiException(FileStoreCode.TEMPLATE_DOES_NOT_EXISTS);
        }

        Task task = new Task();
        String sql = FreemarkerUtil.resolve(template.getSqlSource(), dto.get_PARAMS());
        BeanUtils.copyProperties(dto,task);
        if(Objects.isNull(dto.getTaskName())){
            task.setTaskName(template.getTemplateName());
        }
        //如果文件名不传 则用文件模板上的名称
        if(Objects.isNull(dto.getFileName())){
            task.setFileName(template.getTemplateName() + DateUtil.format(new Date(), "yyyyMMdd") + "." + FileUtil.suffix(template.getTemplateUrl()));
        }
        // 如果没有传文件名后缀 则以模板文件名后缀为准 支持小写格式
        if(!(task.getFileName().endsWith(".xls") || task.getFileName().endsWith(".xlsx") || task.getFileName().endsWith(".csv"))){
            task.setFileName(task.getFileName() + "." + FileUtil.suffix(template.getTemplateUrl()));
        }
        task.setSystemId(template.getSystemId());
        task.setSystemName(template.getSystemName());
        task.setModuleId(template.getModuleId());
        task.setModuleName(template.getModuleName());
        task.setTemplateId(template.getTemplateId());
        task.setIsDelete(Task.DELETE_NO);
        task.setStatus(Task.STATUS_TO_DEAL);
        task.setSql(sql);
        task.setUp();
        log.info("导出任务templateId={},任务执行sql={}",task.getTemplateId(),task.getSql());
        return task;
    }

    private Task dealTaskInfo(TaskDTO dto, BatchImportTemplate template){
        if(Objects.isNull(dto) || Objects.isNull(dto.getTemplateCode())){
            throw new ApiException(FileStoreCode.TEMPLATE_DOES_NOT_EXISTS);
        }

        Task task = new Task();
        BeanUtils.copyProperties(dto,task);
        if(Objects.isNull(dto.getTaskName())){
            task.setTaskName(template.getTemplateName());
        }
        //如果文件名不传 则用文件模板上的名称
        if(Objects.isNull(dto.getFileName())){
            task.setFileName(template.getTemplateName() + "."+ FileUtil.suffix(template.getTemplateUrl()));
        }
        task.setSystemId(template.getSystemId());
        task.setSystemName(template.getSystemName());
        task.setModuleId(template.getModuleId());
        task.setModuleName(template.getModuleName());
        task.setTemplateId(template.getTemplateId());
        task.setIsDelete(Task.DELETE_NO);
        task.setStatus(Task.STATUS_TO_DEAL);
        task.setUp();
        return task;
    }

    private BatchExportTemplate checkExportTemplate(TaskDTO dto) {
        if(Objects.isNull(dto) || Objects.isNull(dto.getTemplateCode())){
            throw new ApiException(FileStoreCode.TEMPLATE_DOES_NOT_EXISTS);
        }
        BatchExportTemplate exportTemplate = exportTemplateService.findByTemplateCode(dto.getTemplateCode());
        if (Objects.isNull(exportTemplate)) {
            throw new ApiException(FileStoreCode.TEMPLATE_DOES_NOT_EXISTS);
        }
        return exportTemplate;
    }

    private BatchImportTemplate checkImportTemplate(TaskDTO dto) {
        if(Objects.isNull(dto) || Objects.isNull(dto.getTemplateCode())){
            throw new ApiException(FileStoreCode.TEMPLATE_DOES_NOT_EXISTS);
        }
        BatchImportTemplate importTemplate = importTemplateService.findByTemplateCode(dto.getTemplateCode());
        if (Objects.isNull(importTemplate)) {
            throw new ApiException(FileStoreCode.TEMPLATE_DOES_NOT_EXISTS);
        }
        return importTemplate;
    }


    @Override
    public void updateById(TaskDTO taskDTO) {
        Task taskDB = taskService.getById(taskDTO.getTaskId());
        BeanUtils.copyProperties(taskDTO,taskDB);
        taskDB.updateSelf();
        taskService.updateById(taskDB);
    }
}
